﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom;

namespace CoderJoe.Dynamic
{
    public class DynamicInvokerGenerator
    {
        public const string InvokeMethodName = "Invoke";
        private const string ClassNameSuffix = "InvokeWrapper";

        public string InstanceFieldName { get; private set; }
        public string ReturnValueFieldName { get; private set; }
        public string ClassName { get { return InvokerType.Name; } }
        public bool IsFunction { get { return Method.ReturnType != typeof(void); } }
        public MethodInfo Method { get; private set; }
        public CodeTypeDeclaration InvokerType { get; private set; }

        public DynamicInvokerGenerator(MethodInfo method)
        {
            if (method == null)
                throw new ArgumentNullException();

            Method = method;

            ReturnValueFieldName = "__" + method.Name + "Result__";
            InstanceFieldName = "__" + method.Name + "Instance__";

            Generate();
        }

        private void Generate()
        {
            InvokerType = new CodeTypeDeclaration(Method.Name + ClassNameSuffix);
            List<CodeFieldReferenceExpression> parameterReferences = new List<CodeFieldReferenceExpression>();

            foreach (ParameterInfo parameter in Method.GetParameters())
            {
                CodeMemberField field = new CodeMemberField(parameter.ParameterType, parameter.Name);
                InvokerType.Members.Add(field);

                parameterReferences.Add(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), field.Name));
            }

            if (IsFunction)
            {
                CodeMemberField field = new CodeMemberField(Method.ReturnType, ReturnValueFieldName);
                InvokerType.Members.Add(field);
            }

            CodeMemberMethod invoke = new CodeMemberMethod();
            invoke.Name = InvokeMethodName;

            CodeMethodInvokeExpression invokeExpression = new CodeMethodInvokeExpression(
                new CodeMethodReferenceExpression(
                    new CodeFieldReferenceExpression(
                        new CodeThisReferenceExpression(), InstanceFieldName),
                        Method.Name), parameterReferences.ToArray());

            invoke.Statements.Add(invokeExpression);
        }
    }
}
